<?php
/**
 * lib for the GraphSpace, Graph Interpreter Project: nth-graph. 
 * @copyright Copyright (c) 2009-2010 Nate Morse
 * @name nthControlGraph class
 * @author Nate Morse <morse.nate@gmail.com>
 * @version 0.03
 * @package nth-graph
 */
include "PHPSandBox.php";

/**
 * @package nth-graph
 *  A PHP interpreter for an nth-control-graph
 */
class nthControlGraph  extends nthGraph {
	var $current_node_index;
	var $template;
	
	
	function __construct($controlGraphName, $env = array()) {
		parent::__construct($controlGraphName);
        $this->environment = $env;
        $this->debug_data("cg environment ", $env);
	}

	/**
	 * @param string $tpl pathname to the template file
	 */
	function setTemplate($tpl) {
		$this->template = $tpl;
	}
	
	/**
	 * Runs a graph by setting inputs, traversing the graph and rendering outputs.
	 */
	function run($channel="to_echo") {
		if ($this->graph['graph']['version'] != '0.03') {
			$this->warn(" <br/> warning graph is not of correct version ");
		}
		$this->setInputs();
		$this->current_node_index = $this->getStartNodeIndex();
		$limit = 500;
		while($limit > 0 && $this->current_node_index >= 0) {
			$this->debug("current_node_index = $this->current_node_index");
			$limit--;
			$this->mergeNodeData($this->current_node_index, $this->get());
			$this->process($this->current_node_index);
			$this->set();
			$this->current_node_index = $this->transition();
		}
		$this->renderOutputs();
	}
	
	/**
	 * @return integer the index of the start node (0)
	 */	 	
	function getStartNodeIndex() {
		return 0;
	}
	
	/**
	 * Gets data from adjacent nodes [graphs]
	 * @return array data from all out going 'get' edges
	 * If the get edge has no name and no alias then all data is collected.
	 * If the edge is named it will attempt to get the nodes data element by that name.
	 * If an alias is given then retreived data will be renamed in the requesting node.
	 */	 	
	function get() {
		$data = array();
		$outEdges = $this->getOutEdges($this->current_node_index, "get");
		foreach($outEdges as $edge_index=>$edge_obj) {
			$edge_name = $edge_obj['name'];
			$edge_alias = $edge_obj['alias'];
			$to_node_index = $edge_obj['to'];
			$sub_type = $edge_obj['subtype'];
			if ($sub_type == "run-process") { // force the target node to run
				// TBD force the node to get data (recursive)...
                $this->process($to_node_index);
			}
			if ($sub_type == "next_row") {
                $this->graph['nodes'][$to_node_index]['_internal_row_index'] += 1;
                $row_i = $this->graph['nodes'][$to_node_index]['_internal_row_index'];
                $this->debug("row_i ".$row_i);
                $name = $this->getNodeAttr($to_node_index, "name");
                if (array_key_exists($row_i, $this->graph['nodes'][$to_node_index]['data'][$name])) {
                    $row_data = $this->graph['nodes'][$to_node_index]['data'][$name][$row_i];
                    $data = $this->_array_merge_recursive2($data, $row_data);
                    $data['nth_row_result'] = true;
                }
                else {
                    $data['nth_row_result'] = false;
                }
			}
			else {
    			if (!$edge_name && !$edge_alias) {
    				$data = $this->_array_merge_recursive2($data, $this->getNodeData($to_node_index));
    			}
    			else { 
    				if (!$edge_name) {
    					$edge_name = $this->getNodeAttr($to_node_index, 'name');
    				}
    				if (!$edge_alias) { 
    					$edge_alias = $edge_name;
    				}
    				$data[$edge_alias] = $this->getNodeDataAttr($to_node_index, $edge_name);
    			}
    		}
		}
		//echo "<br/>get() data = "; var_dump($data);
		return $data;
	}
	
	/**
	 * Processes the internal script in the node
	 * @param integer $node_index the index of the node to be processed 
	 */	 	
	function process($node_index) {
		$this->debug("-- processing node index = ".$node_index);
		if($this->getNodeAttr($node_index, "type") == "queryGraph"){
			$name = $this->getNodeAttr($node_index, "name");
			$path = $this->getNodeAttr($node_index, "path");
			$qg = new nthQueryGraph($path.$name.".qg.json", $this->getNodeData($node_index));
			$this->graph['nodes'][$node_index]['data'][$name] = $qg->query();
			$this->graph['nodes'][$node_index]['_internal_row_index'] = -1;
		}
		if($this->getNodeAttr($node_index, "type") == "controlGraph"){
			$name = $this->getNodeAttr($node_index, "name");
			$path = $this->getNodeAttr($node_index, "path");
			$cg = new nthControlGraph($path.$name.".cg.json", $this->getNodeData($node_index));
			$this->graph['nodes'][$node_index]['data'][$name] = $cg->run("to_data");
		}
		$process_array = $this->getNodeProcess($node_index);
		$this->debug_data("-- process_array = ", $process_array);
		if($process_array != null && is_array($process_array)) {
			foreach($process_array as $code) {
				//print_r($instruction);
				$this->debug('debug:<strong>EVAL CODE using an evaluator object of class:ProcessBox </strong>'.$code.'<br/>');
				if ($code != '') {
					$evaluator =  new ProcessBox($code, $this->graph['nodes'][$node_index]['data']);
					$this->mergeNodeData($node_index, $evaluator->execute());
				}
				$this->debug_data('debug:<strong> after EVAL CODE: Node Data = </strong>', $this->graph['nodes'][$node_index]['data']);
			}
		}
	}
		
	/**
	 * sets data in other nodes via 'set' edges 
	 */	 	
	function set() {
		$outEdges = $this->getOutEdges($this->current_node_index, "set");
		foreach($outEdges as $edge_index=>$edge_obj) {
			$this->setData($edge_obj);
		}
	}

	/**
	 * Finds the next node index by searching though all transition 
	 * edges and evaluating the gaurd on each edge.
	 * The first gaurd to evaluate to true will be the chosen transition.
	 * Otherwise, if no gaurd retuns true, then the first "default" edge will be used.
	 * @return integer The node index of the next node to be visited
	 */	 	
	function transition() {
		$to_node_index = -1;
		$outEdges = $this->getOutEdges($this->current_node_index, "trans");
		foreach($outEdges as $edge_index=>$edge_obj) {
			$guard_result = $this->guard_eval($edge_obj);
			$display_to_node = $edge_obj['to'];
			$this->debug_data("/guard result for edge to $display_to_node = ", $guard_result);
			if ($guard_result === true) {
				$to_node_index = $edge_obj['to'];
				return $to_node_index;
			}
			else {
        // Save the destination of the first edge that reports that it is a 
        // 'default' transition
				if ($guard_result === 'default' && $to_node_index == -1) {
					$to_node_index = $edge_obj['to'];
				}
			}
		}
		//var_dump($outEdges);
		//echo " <br /> transition to node index $to_node_index";
		return $to_node_index;
	}
	
	/**
	 * Evaluates an edge's guard (if it has one defined)
	 * @return bool|string Either the gaurd evaluation (true|false) 
	 * or "default" if no guard is defined	 
	 */	 	
	function guard_eval($edge_obj) {
		if (array_key_exists("guard", $edge_obj)) {
            $code = $edge_obj['guard'];
            $from_node_index = $edge_obj['from'];
            $this->debug("gaurd code = $code ");
            if ($code === 'default') {
               return "default";
            }
            if ($code !== '') {
                if (array_key_exists("nth_row_result", $this->graph['nodes'][$from_node_index])) {
                    $this->graph['nodes'][$from_node_index]['data']['nth_row_result'] = $this->graph['nodes'][$from_node_index]['nth_row_result'];
                }
            	$evaluator =  new GuardBox($code, $this->graph['nodes'][$from_node_index]['data']);
            	return $evaluator->eval_guard();
            }
            return false;
		}
		else {
			return "default";
		}
	}
	
	/**
	 * sets data on a remote node via the $edge_obj
	 * @param array $edge_obj sets data in the node that this edge points to 
	 */	 		
	function setData($edge_obj) {
		$from_node_index = $edge_obj['from'];

		$edge_name = $edge_obj['name'];
		$edge_alias = $edge_obj['alias'];
		$to_node_index = $edge_obj['to'];
		if (!$edge_name) {
			$edge_name = $this->getNodeAttr($to_node_index, 'name');
		}
		if (!$edge_alias) { 
			$edge_alias = $edge_name;
		}
		
		//echo "<br/>getNodeDataAttr($from_node_index, $edge_name)";
		//var_dump($this->graph);
		$value = $this->getNodeDataAttr($from_node_index, $edge_name);
		//echo "<br/>setNodeDataAttr($to_node_index, $edge_alias, $value)";
		$this->setNodeDataAttr($to_node_index, $edge_alias, $value);
	}
		
	/**
	 * Renders all output nodes and templatizes them (if indicated)
	 */	 		
	function renderOutputs($channel = "to_echo") {
		$outData = array();
		foreach($this->graph['nodes'] as $node_index=>$node_obj) {
			if (array_key_exists('type', $node_obj) && $node_obj['type'] == 'out') {
				if (array_key_exists('subtype', $node_obj) && $node_obj['subtype'] == 'template') {
					$this->template = $node_obj['template'];
				}
			
				$outData[$node_obj['name']] = $this->_array_merge_recursive2(
					$outData[$node_obj['name']]
					, $node_obj['data']
				);
			}
		}
		if ($this->template != "") {
			$tpl = & new PHPTemplate($this->template);
			foreach($outData as $data_key=>$data_obj) {
				//echo $data_key." = "; var_dump($data_obj);
				$this->debug_data($data_key, $data_obj);
				$tpl->set($data_key, $data_obj);
			}
			if ($channel === "to_echo") {
			  echo $tpl->render();
			}
			if ($channel === "to_data") {
			   return $tpl->render();
			}
		}
		else {
			if ($channel === "to_echo") {
    			foreach($outData as $data_index=>$data_obj) {
    				//echo $data_index." = ".$data_obj;
    				echo $data_obj['msg'];
    			}
    		}
			if ($channel === "to_data") {
			   return $outData;
			}
		}
		
	}
} 

?>
